/************************************************************************
* events.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#define _WM_EXPOSE_ALL
#include "wm.h"
#include "crypto.h"
#include "list.h"
#include "array.h"
#include "file.h"

#include <string.h>

struct mouse_s {
	int pos[2];
	int rel[2];
	int button[3];
	uint8_t grab;
};

static struct {
	struct mouse_s mouse;
	uint8_t mods;
	action_t *set;
	action_t *actions;
	array_t syms;
} event_data = {
	{
		{0,0},
		{0,0},
		{0,0,0},
		0
	},
	0,
	0,
	NULL
};

static action_t *event_get_action(char* name)
{
	action_t *a = event_data.actions;
	uint32_t h = hash(name);
	while (a) {
		if (h == a->h && !strcmp(name,a->name))
			return a;
		a = a->next;
	}
	return NULL;
}

static int event_sym_active(sym_t *s)
{
	int i;
	sym_t **as = event_data.syms.data;

	for (i=0; i<event_data.syms.length; i++) {
		if (as[i]->type == s->type && as[i]->sym == s->sym)
			return 1;
	}
	return 0;
}

static void event_activate_sym(sym_t *s)
{
	int i;
	sym_t **as = event_data.syms.data;
	sym_t *ss = NULL;

	if (s->type == SYM_TYPE_MOD) {
		event_data.mods |= s->sym;
		return;
	}else if (s->type == SYM_TYPE_MOUSE) {
		if (s->sym == MOUSE_BUTTON_LEFT || s->sym == MOUSE_BUTTON_CENTRE || s->sym == MOUSE_BUTTON_RIGHT)
			event_data.mouse.button[s->sym] = 1;
		return;
	}else if (s->type == SYM_TYPE_NONE) {
		return;
	}

	for (i=0; i<event_data.syms.length; i++) {
		if (!ss && as[i]->type == SYM_TYPE_NONE)
			ss = as[i];
		if (as[i]->type == s->type && as[i]->sym == s->sym)
			return;
	}
	if (!ss) {
		ss = malloc(sizeof(sym_t));
		ss->type = s->type;
		ss->sym = s->sym;
		array_push_ptr(&event_data.syms,ss);
		return;
	}

	ss->type = s->type;
	ss->sym = s->sym;
}

static void event_deactivate_sym(sym_t *s)
{
	int i;
	sym_t **as = event_data.syms.data;

	if (s->type == SYM_TYPE_MOD) {
		event_data.mods &= ~s->sym;
	}else if (s->type == SYM_TYPE_MOUSE) {
		if (s->sym == MOUSE_BUTTON_LEFT || s->sym == MOUSE_BUTTON_CENTRE || s->sym == MOUSE_BUTTON_RIGHT)
			event_data.mouse.button[s->sym] = 0;
		return;
	}else if (s->type == SYM_TYPE_NONE) {
		return;
	}

	for (i=0; i<event_data.syms.length; i++) {
		if (as[i]->type == s->type && as[i]->sym == s->sym) {
			as[i] ->type = SYM_TYPE_NONE;
			return;
		}
	}
}

/* TODO: this shouldn't be */
static void event_exit(event_t *e)
{
	client_state(VLSTATE_EXIT);
}

/* initialise configured events */
int events_init()
{
	array_init(&event_data.syms,ARRAY_TYPE_PTR);
	event_create("forward","w","forward",NULL,NULL,NULL);

	event_create("exit","escape","exit",event_exit,NULL,NULL);

	return 0;
}

/* free events memory */
void events_exit()
{
	action_t *a;
	while (event_data.actions) {
		a = event_data.actions;
		event_data.actions = a->next;
		if (a->com)
			free(a->com);
		free(a);
	}
}

/* save event bindings to file */
void events_save(file_t *f)
{
	char buff[256];
	action_t *a = event_data.actions;
	while (a) {
		if (!kmap_bindtostr(buff,256,&a->bind))
			file_writef(f,"bind %s %s\n",a->name,buff);
		a = a->next;
	}
}

void events_set_mousegrab(uint8_t g)
{
	event_data.mouse.grab = !!g;
}
uint8_t events_get_mousegrab()
{
	return event_data.mouse.grab;
}
void events_get_mouse(int p[2])
{
	p[0] = event_data.mouse.pos[0];
	p[1] = event_data.mouse.pos[1];
}
void events_set_mouse(int x, int y)
{
	event_data.mouse.pos[0] = x;
	event_data.mouse.pos[1] = y;
}

/* set the key bind for an event */
void event_set_bind(char* name, char* value)
{
	char buff[512];
	action_t *a;
	int f = 0;
	bind_t bind;
	uint32_t h;

	if (!value) {
		/* should probably just set to nothing, instead of remove */
		event_remove(name);
		return;
	}

	if (kmap_strtobind(&bind,value))
		return;

	strncpy(buff,name,512);

	h = hash(name);

	a = event_data.actions;
	while (a) {
		if (h == a->h && !strcmp(name,a->name)) {
			kmap_strtobind(&a->bind,value);
			f = 1;
		}else if (kmap_equal(&bind,&a->bind)) {
			kmap_strtobind(&a->bind,"???");
		}
		a = a->next;
	}

	if (f)
		return;

	a = malloc(sizeof(action_t));
	if (!a)
		return;

	kmap_strtobind(&a->bind,value);
	a->func = NULL;
	a->r_func = NULL;
	a->a_func = NULL;
	a->com = strdup(name);
	strncpy(a->name,name,256);
	a->h = h;

	event_data.actions = list_push(&event_data.actions,a);
}

/* set event binding from command */
int event_bind(array_t *a)
{
	char* n;
	char* b;
	if (!a || !a->length)
		return 1;

	n = array_get_string(a,0);
	b = array_get_string(a,1);

	event_set_bind(n,b);

	return 0;
}

/* remove an event */
void event_remove(char* name)
{
	action_t *a = event_get_action(name);
	if (!a)
		return;

	event_data.actions = list_remove(&event_data.actions,a);

	if (a->com)
		free(a->com);
	free(a);
}

/* create a new event action */
void event_create(
	char* name,
	char* bind,
	char* com,
	void (*func)(event_t *e),
	void (*r_func)(event_t *e),
	void (*a_func)(event_t *e)
)
{
	action_t *a;

	a = event_get_action(name);

	if (!a) {
		a = malloc(sizeof(action_t));

		if (!a)
			return;

		a->func = NULL;
		a->r_func = NULL;
		a->a_func = NULL;
		a->com = NULL;
		strcpy(a->name,name);
		a->h = hash(name);
		a->bind.sym.type = SYM_TYPE_NONE;

		event_data.actions = list_push(&event_data.actions,a);
	}

	if (!a->bind.sym.type)
		kmap_strtobind(&a->bind,bind);

	if (com) {
		if (a->com)
			free(a->com);
		a->com = strdup(com);
	}

	if (func)
		a->func = func;
	if (r_func)
		a->r_func = r_func;
	if (a_func)
		a->a_func = a_func;
}

/* trigger all active events */
void events_trigger_active()
{
	event_t e;
	action_t *a = event_data.actions;

	e.type = EVENT_NONE;
	e.sym.type = SYM_TYPE_NONE;
	e.sym.ch = 0;
	e.sym.sym = 0;
	e.x = event_data.mouse.pos[0];
	e.y = event_data.mouse.pos[1];
	e.rx = event_data.mouse.rel[0];
	e.ry = event_data.mouse.rel[1];

	while (a) {
		if (!a->a_func) {
			a = a->next;
			continue;
		}
		if (!event_sym_active(&a->bind.sym)) {
			a = a->next;
			continue;
		}
		if ((event_data.mods&a->bind.mods) != a->bind.mods) {
			a = a->next;
			continue;
		}
		a->a_func(&e);
		a = a->next;
	}
}

void events_handle(event_t *e)
{
	action_t *a;
	bind_t b;
	if (event_data.set && e->type != EVENT_MOUSE_MOTION && e->sym.type != SYM_TYPE_MOD) {
		char buff[512];
		b.sym.type = e->sym.type;
		b.sym.sym = e->sym.sym;
		b.mods = event_data.mods;

		if (kmap_bindtostr(buff,512,&b))
			return;

		event_set_bind(event_data.set->name,buff);
		event_data.set = NULL;
		return;
	}

	if (e->type == EVENT_KEY_DOWN || e->type == EVENT_BUTTON_DOWN) {
		event_activate_sym(&e->sym);
	}else if (e->type == EVENT_KEY_UP || e->type == EVENT_BUTTON_UP) {
		event_deactivate_sym(&e->sym);
	}else if (e->type == EVENT_MOUSE_MOTION) {
		event_data.mouse.rel[0] = e->rx;
		event_data.mouse.rel[1] = e->ry;
		event_data.mouse.pos[0] = e->x;
		event_data.mouse.pos[1] = e->y;
	}

	b.sym.type = e->sym.type;
	b.sym.sym = e->sym.sym;
	b.sym.ch = e->sym.ch;
	b.mods = event_data.mods;

/* TODO: uncomment this
	if (ui_events(e))
		return; */
	if (client_state(VLSTATE_GET) != VLSTATE_PLAY)
		return;

	a = event_data.actions;
	while (a) {
		if (kmap_triggers(&b,&a->bind)) {
			switch (e->type) {
			case EVENT_BUTTON_DOWN:
			case EVENT_KEY_DOWN:
				if (a->func) {
					a->func(e);
				}else if (a->com) {
					command_exec(a->com);
				}
				break;
			case EVENT_BUTTON_UP:
			case EVENT_KEY_UP:
				if (a->r_func)
					a->r_func(e);
				break;
			case EVENT_MOUSE_MOTION:
				if (a->a_func)
					a->a_func(e);
				break;
			default:;
			}
		}
		a = a->next;
	}
}
